﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Concat
{
    [System.Diagnostics.DebuggerNonUserCode]
    static class Util
    {
        public static IEnumerable<U> map<T, U>(this IEnumerable<T> values, Func<T, U> convert)
        {
            foreach (T value in values)
                yield return convert(value);
        }

        public static bool exists<T>(this IEnumerable<T> values, Predicate<T> find)
        {
            foreach (T value in values)
                if (find(value))
                    return true;
            return false;
        }

        public static U reduce<T, U>(this IEnumerable<T> values, Func<T, U> begin, Func<U, T, U> fold)
        {
            bool first = true;
            U result = default(U);
            foreach (T value in values)
            {
                if (first)
                {
                    result = begin(value);
                    first = false;
                }
                else
                    result = fold(result, value);
            }
            return result;
        }
        public static T reduce<T>(this IEnumerable<T> values, Func<T, T, T> fold)
        {
            return values.reduce(x => x, fold);
        }
        public static string concat(this IEnumerable<string> values)
        {
            return values.reduce((l, r) => l + r);
        }

        public static void each<T>(this IEnumerable<T> values, Action<T> action)
        {
            foreach (T value in values)
                action(value);
        }

        public static IEnumerable<T> interleave<T>(this IEnumerable<T> values, T between)
        {
            bool rest = false;
            foreach (T value in values)
            {
                if (rest) yield return between;
                else rest = true;
                yield return value;
            }
        }

        public static IEnumerable<Z> zipWith<T, U, Z>(this IEnumerable<T> ts, IEnumerable<U> us,
            Func<T, U, Z> zip)
        {
            IEnumerator<T> iterT = ts.GetEnumerator();
            IEnumerator<U> iterU = us.GetEnumerator();

            while (true)
                if (iterT.MoveNext() && iterU.MoveNext())
                    yield return zip(iterT.Current, iterU.Current);
                else yield break;
        }
        public static IEnumerable<Tuple<T, U>> zipWith<T, U>(this IEnumerable<T> ts, IEnumerable<U> us)
        {
            return ts.zipWith(us, (t, u) => new Tuple<T, U>(t, u));
        }

        public static HashSet<T> combine<T>(this IEnumerable<HashSet<T>> sets)
        {
            HashSet<T> combined = new HashSet<T>();
            foreach (HashSet<T> set in sets)
                foreach (T item in set)
                    combined.Add(item);
            return combined;
        }

        public static string prettify(this Type type)
        {
            return type.prettify(new Dictionary<string, string>());
        }
        public static string prettify(this Type type, Dictionary<string, string> renaming)
        {
            string str = type.Name;
            if (str.Contains('`'))
            {
                str = str.Remove(str.IndexOf('`'));
                str = rename(str, renaming);
                str += "["
                    + type.GetGenericArguments()
                          .map(gtype => prettify(gtype, renaming))
                          .interleave(", ")
                          .concat()
                    + "]";
            }
            else
                str = rename(str, renaming);
            return str;
        }
        private static string rename(string name, Dictionary<string, string> renaming)
        {
            return renaming.ContainsKey(name) ? renaming[name] : name;
        }

        // synchronously waits for an IHasEvent's event to fire
        public static void waitEvent(this IHasEvent ihe, Action beforeWait)
        {
            ManualResetEvent mre = new ManualResetEvent(false);
            ihe.addCallback(() => mre.Set());
            beforeWait();
            mre.WaitOne();
        }
    }
}
